package jmind.core.aspect;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import jmind.base.cache.ICache;
import jmind.base.cache.LocalCache;
import jmind.base.util.DataUtil;
import jmind.base.util.GlobalConstants;
import jmind.base.annotation.CacheMonitor;
import jmind.core.cache.support.GuavaCache;
import jmind.core.geo.GLocation;
import jmind.core.log.LogUtil;
import jmind.core.manager.XMemCacheManager;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.interceptor.CacheAspectSupport;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * http://weiboxie.iteye.com/blog/1985209
 * 基于SpEL 解析key 的缓存形式
 * spel #表示参数对象， @可以引用spring注解对象 #root表示跟对象，#this表示本身。还可以用 < > and or 等等
 * @CacheMonitor(name = "cacheName", prefix = "userid",key="#user.cid,#name",exp=3600)
 * @CacheMonitor(name = "cacheName", key = "'userid_'+#userId")
 * @author wbxie
 * @see CacheAspectSupport http://sishuok.com/forum/blogPost/list/2463.html
 *  http://www.cnblogs.com/rollenholt/p/4202631.html
 * @see Cacheable
 * @see CacheEvict
 * 扫描表达式配置 http://www.cnblogs.com/leiOOlei/p/3613352.html
 * spring aop 这种实现方式有内部调用问题，即类本身调用aop的方法不会被aop 
 * 解决方案参考 http://blog.csdn.net/wxwzy738/article/details/27566645
1、增加配置
<aop:aspectj-autoproxy proxy-target-class="true" />
<aop:config expose-proxy="true"  />
 2、修改我们的业务实现类
this.b();-----------修改为--------->((AService) AopContext.currentProxy()).b();

3、
或者实现 自定义BeanSelfAware 接口。配置 扫描 InjectBeanSelfProcessor类
 * 2013-12-4
 */
//@Component
//  @Aspect
public abstract class CacheSpELAspect {

    public  abstract ICache getCache(CacheMonitor monitor) ;
    protected final Logger logger = LoggerFactory.getLogger(getClass());


    final ExpressionParser parser = new SpelExpressionParser();
    final ConcurrentMap<String, List<Expression>> expressionMap = Maps.newConcurrentMap();



    private final List<Expression> getExpressions(String spel) {
        List<Expression> list = expressionMap.get(spel);
        if (list == null) {
            list = Lists.newArrayList();
            StringTokenizer token = new StringTokenizer(spel, GlobalConstants.COMMA);
            while (token.hasMoreTokens()) {
                list.add(parser.parseExpression(token.nextToken()));
            }
            expressionMap.put(spel, list);
        }
        return list;

    }








    @Around(value = "execution(* *(..)) && @annotation(monitor)", argNames = "pjp,monitor")
    public Object doAround(ProceedingJoinPoint pjp,CacheMonitor monitor) throws Throwable {


        //  final CacheMonitor m = JoinPoints.getAnnotation(pjp, CacheMonitor.class);
        String key;
        if (monitor.key().isEmpty()) {
            Object[] values = pjp.getArgs();
            // 如果入参只有一个且为Collection, 返回值为Map, 则调用collectionCache方法,支持 多key 查询
            if(values.length == 1 && values[0] instanceof Collection && Map.class.isAssignableFrom(((MethodSignature) pjp.getSignature()).getReturnType())) {
                return collectionCache(pjp, monitor, values);
            }
            key = DataUtil.composeKey(monitor.prefix(), pjp.getArgs());
        } else {
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            StandardEvaluationContext context = new StandardEvaluationContext();
            String[] names = signature.getParameterNames(); // 参数变量名
            Object[] values = pjp.getArgs(); // 参数值
            for (int i = 0; i < names.length; i++) {
                context.setVariable(names[i], values[i]);
            }

            StringBuilder sb = new StringBuilder(monitor.prefix());
            List<Expression> expressions = getExpressions(monitor.key());
            for (Expression exp : expressions) {
                sb.append(GlobalConstants.DASH).append(exp.getValue(context));
            }
            key = sb.toString();
        }



        ICache cache = getCache(monitor);
        if(cache==null){
            logger.warn(monitor.type()+" cache is null "+key);
            return pjp.proceed();
        }
        //添加参数值 做为key
        if (monitor.remove()) {
            Object object = pjp.proceed();
            cache.delete(key);
            return object;
        }



        return cache.computeIfAbsent(key, monitor.exp(), k -> {
            try {
                logger.info("put cachekey="+key);
                return pjp.proceed();
            } catch (Throwable throwable) {
                logger.error("",throwable);
                return null;
            }
        });


    }



    /**
     * 代码描述 : 入参为Collection时的缓存方法
     * @author subo177693
     * @param values 切入的方法的入参
     * date : 2018/7/25
     * modified by : [变更日期YYYY-MM-DD][更改人姓名][变更描述]
     */
    private Map collectionCache(ProceedingJoinPoint pjp, CacheMonitor monitor, Object[] values)
        throws Throwable {

        //获取入参collection
        Collection inputCollection = (Collection) values[0];
        // 获取缓存对象
        ICache cache = getCache(monitor);
        if(cache==null){
            logger.warn(monitor.type() + " cache is null " + Arrays.toString(values));
            return (Map) pjp.proceed();
        }

        // key的集合
        Collection<String> keyCollection = new ArrayList<>(inputCollection.size());
        // key与入参的对应关系
        Map<Object, String> inputAndkeyRelation = new HashMap<>(inputCollection.size());
        for(Object o : inputCollection) {
            String singleKey = DataUtil.composeKey(monitor.prefix(), o);
            keyCollection.add(singleKey);
            inputAndkeyRelation.put(o, singleKey);
        }
        // 从缓存获取有缓存的map
        Map<String, ?> multiResult = cache.getMulti(keyCollection);
        // 有缓存的结果map
        Map cacheResult = new HashMap(inputCollection.size());
        Collection inputCollectionNew=newColection(inputCollection);
        for(Object o : inputCollection) {
            Object cacheValue=multiResult.get(inputAndkeyRelation.get(o));
            if(cacheValue!=null){
                cacheResult.put(o,cacheValue);
            }else{
                inputCollectionNew.add(o);
            }
        }

        // 入参去除有缓存的input参数
        if(inputCollectionNew.isEmpty()){
            return cacheResult;
        }
        // 重新赋值入参
        values[0] = inputCollectionNew;
        // 对剩余的input参数执行方法
        Map<Object, Object> noCacheResult = (Map) pjp.proceed(values);
        for (Map.Entry entry : noCacheResult.entrySet()) {
            cache.set(inputAndkeyRelation.get(entry.getKey()), monitor.exp(),entry.getValue());
        }
        // 合并结果
        cacheResult.putAll(noCacheResult);
        return cacheResult;
    }


    private   Collection newColection(Collection inputCollection){
        Collection collection = null;
        try {
            collection = inputCollection.getClass().newInstance();
        } catch (Exception e) {
            collection=inputCollection instanceof List? new ArrayList():new HashSet();
        }

        return collection;
    }


    /**
     * SpEL 解析列子
     * org.springframework.cache.interceptor.ExpressionEvaluator
     * http://sishuok.com/forum/blogPost/list/2463.html
     * 2013-12-4
     */
      void expression() {
        ExpressionParser parser = new SpelExpressionParser();
        //1.给root对象赋值 
        GLocation root = new GLocation();
        root.setCity("湖南");
        root.setX(101);

        GLocation user = new GLocation();
        user.setCity("beijing");
        user.setX(12);
        StandardEvaluationContext context = new StandardEvaluationContext(root);
        context.setVariable("userId", 101);
        context.setVariable("user", user);
        Object value = parser.parseExpression("#userId + 'findById'").getValue(context);
        System.out.println(value);

        value = parser.parseExpression("#user.city").getValue(context);
        System.out.println(value);

        String result1 = parser.parseExpression("#root.city").getValue(context, String.class);
        System.out.println(result1);

        Object result = parser.parseExpression("#this").getValue(context);
        System.out.println(result);

    }


}
